import java.math.BigDecimal;
import java.math.RoundingMode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * 计算器工具类
 * 
 * @author Zero
 * 
 */
public class CalculatorUtils {

	public static final Map<Character, Integer> symLvMap = new HashMap<>(9);// 符号优先级map
	private static String symStr; // 运算符字符串
	private static List<BigDecimal> numList; // 数字集合

	// 静态代码块
	static {
		symLvMap.put('=', 0);
		symLvMap.put('-', 1);
		symLvMap.put('+', 1);
		symLvMap.put('×', 2);
		symLvMap.put('÷', 2);
		symLvMap.put('%', 3);
		symLvMap.put('^', 3);
		symLvMap.put('(', 4);
		symLvMap.put(')', 1);
	}

	// 禁止生产对象
	private CalculatorUtils() {
	}

	/**
	 * 计算器主要部分——计算函数
	 * 
	 * @param expressionStr 表达式
	 * @return 结果字串
	 */
	public static String calculate(String expressionStr) {
		symStr = null;
		numList = new ArrayList<>();

		// 检查表达式
		prepareParam(expressionStr);

		// 符号栈
		Stack<Character> symStack = new Stack<>();
		// 数字栈
		Stack<BigDecimal> numStack = new Stack<>();

		// numList的标志位
		int i = 0;
		// symStr的标志位
		int j = 0;
		// 运算符号
		char sym = '\u0000';
		// 运算符号前后两个数
		BigDecimal num1 = null, num2 = null;

		// 形如：=1=，则退出循环，结果为1
		while (symStack.empty() || !(symStack.lastElement() == '=' && symStr.charAt(j) == '=')) {
			if (symStack.empty()) {
				symStack.push('=');
				numStack.push(numList.get(i++));
			}

			// 比较符号优先级，若当前符号优先级大于前一个则压栈
			if (symLvMap.get(symStr.charAt(j)) > symLvMap.get(symStack.lastElement())) {
				if (symStr.charAt(j) == '(') {
					symStack.push(symStr.charAt(j++));
					continue;
				}
				numStack.push(numList.get(i++));
				symStack.push(symStr.charAt(j++));
			} else {// 当前符号优先级小于等于前一个符号的优先级

				// 若（）之间没有符号，则“（”出栈
				if (symStr.charAt(j) == ')' && symStack.lastElement() == '(') {
					j++;
					symStack.pop();
					continue;
				}
				// “（”直接压栈
				if (symStack.lastElement() == '(') {
					numStack.push(numList.get(i++));
					symStack.push(symStr.charAt(j++));
					continue;
				}

				num2 = numStack.pop();
				num1 = numStack.pop();
				sym = symStack.pop();

				// 匹配运算符
				switch (sym) {
				case '+':
					numStack.push(add(num1, num2));
					break;
				case '-':
					numStack.push(sub(num1, num2));
					break;
				case '×':
					numStack.push(mul(num1, num2));
					break;
				case '÷':
					if (num2.equals(BigDecimal.ZERO)) {// 除数为0
						throw new CalculatorException.MathException("存在除数为0！");
					}
					numStack.push(div(num1, num2));
					break;
				case '%':
					if (num2.equals(BigDecimal.ZERO)) {// 除数为0
						throw new CalculatorException.MathException("存在除数为0！");
					}
					numStack.push(rem(num1, num2));
					break;
				case '^':
					numStack.push(pow(num1, num2));
					break;
				}
			}
		}
		return numStack.pop().stripTrailingZeros().toString();
	}

	/**
	 * 参数准备
	 * 
	 * @param expressionStr 表达式
	 */
	private static void prepareParam(String expressionStr) {
		// 空值校验
		if (expressionStr == null || expressionStr.substring(0, expressionStr.length() - 1).isBlank()) {
			throw new CalculatorException.NullException("输入的值为空！");
		}

		// 预处理
		// 去空格
		expressionStr = expressionStr.replaceAll("\\s*", "");
		// 开头为负数，如-1，改为0-1
		if (expressionStr.startsWith("-")) {
			expressionStr = "0" + expressionStr;
		}

		// 校验格式
		if (!checkFormat(expressionStr)) {
			throw new CalculatorException.ExpressionException("表达式错误！");
		}

		// 处理表达式，改为标准表达式
		expressionStr = changeToStandardFormat(expressionStr);

		// 拆分符号和数字
		String[] nums = expressionStr.split("[^.0-9]");
		for (int i = 0; i < nums.length; i++) {
			if (!nums[i].isBlank()) {
				numList.add(new BigDecimal(nums[i]));
			}
		}
		symStr = expressionStr.replaceAll("[.0-9]", "");
	}

	/**
	 * 检查表达式格式是否正确
	 * 
	 * @param str 表达式
	 * @return
	 */
	private static boolean checkFormat(String str) {
		// 校验是否以“=”结尾
		if (!str.endsWith("=")) {
			return false;
		}
		// 校验开头是否为数字或者“(”
		if (!(isCharNum(str.charAt(0)) || str.startsWith("("))) {
			return false;
		}
		// 校验
		char c = ' ';
		for (int i = 1; i < str.length() - 1; i++) {
			c = str.charAt(i);
			// 字符合法判断
			if (!isLegalChar(c)) {
				return false;
			}
			if (!(isCharNum(c))) {
				if (c == '+' || c == '-' || c == '×' || c == '÷' || c == '%' || c == '^') {
					// 1*(-2+3)的情况
					if (c == '-' && str.charAt(i - 1) == '(') {
						continue;
					}
					// 若符号前一个不是数字或者“）”时
					if (!(isCharNum(str.charAt(i - 1)) || str.charAt(i - 1) == ')')) {
						return false;
					}
				}
				// 校验“.”的前后是否位数字
				if (c == '.') {
					if (!isCharNum(str.charAt(i - 1)) || !isCharNum(str.charAt(i + 1))) {
						return false;
					}
				}
			}
		}
		// 校验括号是否配对
		return isBracketPair(str);
	}

	/**
	 * 处理表达式格式为标准格式，如2(-1+1)(2+1)改为2*(0-1+1)*(2+1)
	 * 
	 * @param str 表达式
	 * @return 标准表达式
	 */
	private static String changeToStandardFormat(String str) {
		StringBuilder sb = new StringBuilder(str);
		char c = ' ';
		for (int i = 0; i < sb.length(); i++) {
			c = sb.charAt(i);
			if (i != 0 && c == '(' && (isCharNum(sb.charAt(i - 1)) || sb.charAt(i - 1) == ')')) {
				sb.insert(i, '×');
				continue;
			}
			if (c == '-' && sb.charAt(i - 1) == '(') {
				sb.insert(i, '0');
				continue;
			}
		}
		return sb.toString();
	}

	/**
	 * 校验括号是否配对
	 * 
	 * @param str 表达式
	 * @return 如果括号配对返回true，反之则反
	 */
	private static boolean isBracketPair(String str) {
		LinkedList<Character> linkedList = new LinkedList<>();
		for (char c : str.toCharArray()) {
			if (c == '(') {
				linkedList.add(c);
			} else if (c == ')') {
				if (linkedList.isEmpty()) {
					return false;
				}
				linkedList.removeLast();
			}
		}
		if (linkedList.isEmpty()) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 校验字符是否合法
	 * 
	 * @param c
	 * @return
	 */
	private static boolean isLegalChar(Character c) {
		if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '×' || c == '÷' || c == '%' || c == '^' || c == '.'
				|| c == '(' || c == ')') {
			return true;
		}
		return false;
	}

	/**
	 * 判断字符是否为数字字符
	 */
	private static boolean isCharNum(Character c) {
		if (c >= '0' && c <= '9') {
			return true;
		}
		return false;
	}

	/**
	 * 加
	 * 
	 * @param num1
	 * @param num2
	 * @return
	 */
	private static BigDecimal add(final BigDecimal num1, final BigDecimal num2) {
		return num1.add(num2);
	}

	/**
	 * 减
	 * 
	 * @param num1
	 * @param num2
	 * @return
	 */
	private static BigDecimal sub(final BigDecimal num1, final BigDecimal num2) {
		return num1.subtract(num2);
	}

	/**
	 * 乘
	 * 
	 * @param num1
	 * @param num2
	 * @return
	 */
	private static BigDecimal mul(final BigDecimal num1, final BigDecimal num2) {
		return num1.multiply(num2);
	}

	/**
	 * 除
	 * 
	 * @param num1
	 * @param num2
	 * @return
	 */
	private static BigDecimal div(final BigDecimal num1, final BigDecimal num2) {
		return num1.divide(num2, 15, RoundingMode.HALF_UP);
	}

	/**
	 * 余
	 * 
	 * @param num1
	 * @param num2
	 * @return
	 */
	private static BigDecimal rem(final BigDecimal num1, final BigDecimal num2) {
		return num1.remainder(num2);
	}

	/**
	 * 幂
	 * 
	 * @param num1
	 * @param num2
	 * @return
	 */
	private static BigDecimal pow(final BigDecimal num1, final BigDecimal num2) {
		return num1.pow(num2.intValue());
	}
}